// OpAnnotationDwgLead.ts

import {
  Vector3,
  Font,
  Scene,
  Shape,
  ShapeGeometry,
  Mesh,
  TextGeometry,
  Raycaster,
  Material,
} from "../../WebViewer/THREE/Three";
import { AnnotationMesh, OpAnnotationDwg } from "./OpAnnotationDwg";

class OpAnnotationDwgLead extends OpAnnotationDwg {
  finishcallback: Function;

  constructor(app: any) {
    super(app);

    this.finishcallback = () => {};
  }

  bindFinishCallback(callback: Function) {
    if (callback instanceof Function) {
      this.finishcallback = callback;
    }
  }

  tempAnnotationPointerUp() {
    if (!this.tempAnnotation) return;
    const length = this.tempAnnotation.ptsArr.length - 1;
    let start = this.tempAnnotation.ptsArr[length - 1].clone();
    let end = this.tempAnnotation.ptsArr[length].clone();
    let distance = start.distanceTo(end);
    if (distance === 0) return;

    super.stopAnnotation();

    this.finishcallback();
  }

  createNewAnnotation(point: Vector3) {
    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationLeadMesh({
      start: point,
      end: point,
      font,
      scene,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
    });
    return mesh;
  }

  createAnnotationFromData(data: {
    ptsArr: Array<number[]>;
    lineSize: number;
    textSize: number;
    color: string;
    text: string;
  }) {
    let pointArr: Vector3[] = [];
    data.ptsArr.forEach((item: number[]) => {
      const v3 = new Vector3().fromArray(item);
      pointArr.push(v3);
    });

    const scene = this.app.bimViewer.getIncrementalScene();
    const font = this.app.bimViewer.getRenderContext().getWebGLFont();
    const mesh = new AnnotationLeadMesh({
      start: pointArr[0],
      end: pointArr[1],
      font,
      scene,
      lineSize: data.lineSize,
      textSize: data.textSize,
      color: data.color,
    });

    mesh.createLeadText(data.text);
    const pixelScale2d = this.app.bimViewer.mainCamera.getPixelScale2d();
    mesh.updateLineTextShape(pixelScale2d);

    mesh.setDragHandleVisible(false);

    return mesh;
  }
}

class AnnotationLeadMesh extends AnnotationMesh {
  textMesh!: Mesh;
  text: string;

  constructor(param: {
    start: Vector3;
    end: Vector3;
    font: Font;
    scene: Scene;
    lineSize: number;
    textSize: number;
    color: string;
  }) {
    super(param);

    this.text = "";

    this._createLineTextShape();
  }

  private _createLineTextShape() {
    const shape = new Shape();
    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape = new Mesh(shapeGeo, this.material);
    this.scene.add(this.lineShape);

    this.ptsArr.forEach((point) => {
      const drag = this.createDragHandle(point);
      this.dragHandleArr.push(drag);
      drag.install(this.scene);
    });
  }

  createLeadText(text: string) {
    this.text = text;

    const textGeo = new TextGeometry(text, {
      font: this.font,
      size: this.textSize,
      height: 0.001,
      curveSegments: 10,
    });
    this.textMesh = new Mesh(textGeo, this.material);
    this.textMesh.geometry.computeBoundingBox();

    this.scene.add(this.textMesh);

    this.textMesh.position.copy(this.ptsArr[1]);
  }

  updateLineTextShape(pixelScale2d: number) {
    pixelScale2d = pixelScale2d * 0.5 * (this.lineSize + 1);
    if (pixelScale2d < 0.00099) pixelScale2d = 0.00099;

    const direction = this.ptsArr[1]
      .clone()
      .sub(this.ptsArr[0])
      .normalize()
      .multiplyScalar(pixelScale2d);
    const normal = new Vector3(0, 0, 1)
      .cross(direction)
      .normalize()
      .multiplyScalar(pixelScale2d);

    const normal2 = normal.clone().multiplyScalar(5);
    const direction2 = direction.clone().multiplyScalar(12);

    if (
      this.ptsArr[0].distanceTo(this.ptsArr[1]) <
      this.ptsArr[0].distanceTo(
        new Vector3().copy(this.ptsArr[0]).add(direction2)
      )
    ) {
      return;
    }

    const l1 = new Vector3().copy(this.ptsArr[0]);
    const l2 = new Vector3().copy(this.ptsArr[0]).add(direction2).sub(normal2);
    const l3 = new Vector3().copy(this.ptsArr[0]).add(direction2).sub(normal);
    const l4 = new Vector3().copy(this.ptsArr[1]).sub(normal);
    const l5 = new Vector3().copy(this.ptsArr[1]).add(normal);
    const l6 = new Vector3().copy(this.ptsArr[0]).add(direction2).add(normal);
    const l7 = new Vector3().copy(this.ptsArr[0]).add(direction2).add(normal2);

    const shape = new Shape();
    shape.moveTo(l1.x, l1.y);
    shape.lineTo(l2.x, l2.y);
    shape.lineTo(l3.x, l3.y);
    shape.lineTo(l4.x, l4.y);
    shape.lineTo(l5.x, l5.y);
    shape.lineTo(l6.x, l6.y);
    shape.lineTo(l7.x, l7.y);
    shape.closePath();

    const shapeGeo = new ShapeGeometry(shape);
    this.lineShape.geometry.dispose();
    this.lineShape.geometry = shapeGeo;
  }

  updateDragShape(current: number) {
    this.dragHandleArr[current + 1].setPosition(this.ptsArr[current + 1]);

    this.textMesh?.position.copy(this.ptsArr[1]);
  }

  setMeshVisible(value: boolean) {
    if (value === false) {
      this.dragHandleArr.forEach((item) => {
        item.setVisible(value);
      });
    }

    this.lineShape.visible = value;
    if (this.textMesh) this.textMesh.visible = value;
  }

  dispose() {
    super.dispose();
    this.scene.remove(this.textMesh);
  }

  intersectObjects(_rayCaster: Raycaster) {
    let children: any = [];
    children.push(this.lineShape);
    if (this.textMesh) children.push(this.textMesh);

    const intersects = _rayCaster.intersectObjects(children, true);
    if (intersects.length > 0) {
      return true;
    }

    return false;
  }

  changeMeshMaterial(material: Material) {
    if (this.lineShape.material === material) {
      return;
    }

    this.lineShape.material = material;
    if (this.textMesh) this.textMesh.material = material;
  }

  getData() {
    let pointArr: any = [];
    this.ptsArr.forEach((item) => {
      const arr = item.toArray();
      pointArr.push(arr);
    });

    let obj = {
      ptsArr: pointArr,
      lineSize: this.lineSize,
      textSize: this.textSize,
      color: this.color,
      text: this.text,
    };

    return obj;
  }
}

export { OpAnnotationDwgLead };
